<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<title>五子棋</title>
	</head>
	<body>
		<!--棋盘层-->
		<canvas id="gobangBase" width="1200px" height="800px" style="position: absolute;"></canvas>
		<!--棋子层-->
		<canvas id="gobangPice" width="1200px" height="800px" style="position: absolute;"></canvas>
		<!--交互层-->
		<canvas id="gobangActi" width="1200px" height="800px" style="position: absolute;"></canvas>
	</body>
	<script>
		//游戏基本架构

		var gbb = gobangBase.getContext("2d");
		var gbp = gobangPice.getContext("2d");
		var gba = gobangActi.getContext("2d");
		var gbm = [];
		var isEnd = 0;
		var pices = [2, [], [] ];

		//玩家使用的棋子
		var player = 2;

		function isEmpty(x, y) {
			//判断该位置是否有棋子
			return isEnd == 0 && gbm[x] && gbm[x][y] == 0;
		}


		function trans(X) {
			//坐标转换为像素坐标
			return 25 + 50 * X;
		}

		function toX(P) {
			//像素坐标转换为坐标
			return Math.round((P - 25) / 50);
		}

		function drawBase() {
			//绘制棋盘

			//画线
			for (let i = 0; i < 16; i++) {

				gbb.moveTo(25, trans(i));
				gbb.lineTo(775, trans(i));

				gbb.moveTo(trans(i), 25);
				gbb.lineTo(trans(i), 775);

				//小黑点
				if (i == 7 || i == 3 || i == 12) {
					gbb.fillRect(trans(i) - 5, trans(i) - 5, 10, 10);
					if (i != 7) {
						gbb.fillRect(trans(i) - 5, trans(15 - i) - 5, 10, 10);
					}
				}

			}
			gbb.stroke();

			//初始化棋盘数组
			var row = [];
			for (let i = 0; i < 16; i++) {
				row.push(0);
			}

			for (let i = 0; i < 16; i++) {
				gbm.push([].concat(row));
			}

		}

		drawBase();

		var old = [0, 0];

		gobangActi.onmousemove = (e) => {

			//棋盘内移动时，绘制定位光标
			let x = toX(e.clientX);
			let y = toX(e.clientY);

			if (isEnd == 0) {
				gba.clearRect(trans(old[0]) - 15, trans(old[1]) - 15, 30, 30);
			}

			if (isEmpty(x, y)) {
				gba.strokeRect(trans(x) - 10, trans(y) - 10, 20, 20);
				old = [x, y];
			}

		}

		gobangActi.onclick = (e) => {

			if (pices[0] == player) {
				let x = toX(e.clientX);
				let y = toX(e.clientY);
				if(isEmpty(x,y)){
					drawPice(x, y, pices[0]);
					setTimeout(() => {
						gbAi(gbm, pices, player == 1 ? 2 : 1, drawPice);
					}, 10);
				}
			}

		}
		
		function drawArea(x,y){
			gbp.strokeStyle = "#F00";
			gbp.strokeRect(trans(x) - 15, trans(y) - 15,30,30);
			gbp.strokeStyle = "#000";
		}

		function drawPice(x, y, pice) {

			//绘制棋子
			if (isEmpty(x, y)) {

				gba.clearRect(trans(old[0]) - 15, trans(old[1]) - 15, 30, 30);

				let p = pice == 1 ? 2 : 1;
				let len = pices[p].length - 1;
				if (len > -1) {
					gba.clearRect(trans(pices[p][len][0]) - 30, trans(pices[p][len][1]) - 30, 60, 60);
				}

				gbp.fillStyle = "#000";
				gbp.beginPath();
				gbp.arc(trans(x), trans(y), 20, 0, 2 * Math.PI);
				gbp.fill();

				//白棋
				if (pice == 1) {
					gbp.fillStyle = "#FFF";
					gbp.beginPath();
					gbp.arc(trans(x), trans(y), 18, 0, 2 * Math.PI);
					gbp.fill();
				}

				gbm[x][y] = pice;
				pices[pice].push([x, y]);

				gba.strokeRect(trans(x) - 25, trans(y) - 25, 50, 50);

				pices[0] = pice == 1 ? 2 : 1;

				checkWin(x, y);
			}

		}

		function drawWin(x1, y1, x2, y2) {

			//游戏结束
			let s = 1;
			let e = 2;
			if (x1 == x2) {
				if (y1 > y2) {
					s = 0;
					e = 1;
				}
			} else if (y1 == y2) {
				if (x1 > x2) {
					s = 1.5;
					e = 0.5;
				} else {
					s = 0.5;
					e = 1.5;
				}
			} else if (x1 > x2) {
				if (y1 > y2) {
					s = 1.75;
					e = 0.75;
				} else {
					s = 1.25;
					e = 0.25;
				}
			} else {
				if (y1 > y2) {
					s = 0.25;
					e = 1.25;
				} else {
					s = 0.75;
					e = 1.75;
				}
			}

			let p = pices[0] == 1 ? 2 : 1;
			let len = pices[p].length - 1;
			if (len > -1) {
				gba.clearRect(trans(pices[p][len][0]) - 30, trans(pices[p][len][1]) - 30, 60, 60);
			}

			gba.lineWidth = 3;

			gba.strokeStyle = "#F00";

			gba.beginPath();

			gba.arc(trans(x1), trans(y1), 22, s * Math.PI, e * Math.PI);
			gba.arc(trans(x2), trans(y2), 22, e * Math.PI, s * Math.PI);
			gba.arc(trans(x1), trans(y1), 22, s * Math.PI, s * Math.PI);
			gba.stroke();

			gba.strokeStyle = "#000";

			isEnd = 1;
			gba.lineWidth = 1;
			playWin();
		}

		function playWin() {
			//播放结果动画
			let start = 0;

			let w = 60;
			let h = 10;

			gba.clearRect(trans(old[0]) - 15, trans(old[1]) - 15, 30, 30);

			let loop = () => {

				start += 1;

				let x = 1000 - (w / 2 + start);
				let y = 125 - (h + start) / 2;

				gba.fillStyle = "#000";
				gba.fillRect(x, y, w + start * 2, h + start);
				gba.fillStyle = "#FFF";
				gba.fillRect(x + 3, y + 3, (w - 6) + start * 2, (h - 6) + start);

				if (start < 100) {
					setTimeout(function() {
						loop();
					}, 2);
				} else {
					gba.font = "45px Arial";
					if (pices[0] == 2) {
						gba.strokeText("白棋获胜", x + 36, y + 70);
					} else {
						gba.strokeText("黑棋获胜", x + 36, y + 70);
					}
				}
			}
			loop();
		}

		function checkWin(x, y) {
			//游戏结束检测
			let p = gbm[x][y];

			let count = 0;
			let e = [x, y];
			
			let ways = [
					[[1,0],[-1,0]],
					[[0,1],[0,-1]],
					[[1,1],[-1,-1]],
					[[1,-1],[-1,1]]
			];
			
			for(let i = 0; i < 4; i++) {
				
				let way = ways[i];
				let res = counter(gbm,p,x,y,way[0][0],way[0][1],0,4);
				
				if(res[0] == 4){
					drawWin(x,y,res[1],res[2]);
					return;
				}
				
				e = [res[1],res[2]];
				
				res = counter(gbm,p,x,y,way[1][0],way[1][1],0,4);
				
				if((res[0] + count) >= 4){
					drawWin(e[0],e[1],res[1] - (res[0] + count - 4) * way[1][0],res[2] - (res[0] + count - 4) * way[1][1]);
					return;
				}
				
			}
			
		}
		
		function counter(m,t,x,y,xw,yw,z,c){
			//方向计数器 m 地图数组 t 目标值 x,y 起点坐标 xw,yw 坐标前进方向 z 允许空格数 c 检测格数
			let count = 0;
			let ex = -1;
			let ey = -1;
			let end = -1;
			
			for(let i = 1; i <= c; i++ ){
				ex = x+xw*i;
				ey = y+yw*i;
				if(m[x+xw*i]){
					let n = m[x+xw*i][y+yw*i];
					end = n;
					if(n == t){
						count++;
					}else if( n == 0 && z > 0){
						z--;
					}else{
						ex = x+xw*(i-1);
						ey = y+yw*(i-1);
						break;
					}
				}else{
					ex = x+xw*(i-1);
					ey = y+yw*(i-1);
					break;
				}
			}
			return [count,ex,ey,end];
		}
	</script>

	<script>
		//五子棋AI
		function gbAi(map, pices, t, drawPice){
			let coor = GbGen.desition(map,t,pices[t],pices[t == 1 ? 2 : 1]);
			drawPice(coor[0],coor[1],t);
		}
		
		var GbGen = {
				
				gen:[[5,10],[100,1000],[1000,10000],[500000,0]],
				
				desition:function(map, t, p1, p2) {
					
					let coor = [7,7];
					
					let ways = [ [1,0],[-1,0], [0,1],[0,-1], [1,1],[-1,-1], [1,-1],[-1,1] ];
					
					let m = {};
					let set = {};
					
					for(let pi in p1) {
						
						let p = p1[pi];
						for(let i = 1; i < 3; i++) {
							
							for(let wi in ways) {
								let w = ways[wi];
								let x = p[0] + i * w[0];
								let y = p[1] + i * w[1];
								
								if(x < 0 || x > 15 || y < 0 || y > 15) {
									continue;
								}
								
								let n = [x,y].join(",");
																
								if( map[x][y] == 0 && !m[n]) {
									let s = this.denfen(map,t,x,y);
									m[n] = s + 1;
								}
							}
						}
						
					}
					
					for(let pi in p2) {
						
						let p = p2[pi];
						for(let i = 1; i < 3; i++) {
							
							for(let wi in ways) {
								
								let w = ways[wi];
								let x = p[0] + i * w[0];
								let y = p[1] + i * w[1];
								
								if(x < 0 || x > 15 || y < 0 || y > 15) {
									continue;
								}
								
								let n = [x,y].join(",");
								
								if( map[x][y] == 0 && !set[n] ) {
									set[n]=true;
									let s = this.denfen(map,t == 1 ? 2 : 1,x,y);
									if(m[n]) {
										m[n] += s;
									}else {
										m[n] = s;
									}
								}
							}
						}
						
					}
					
					set = [];
					let max = 0;
					
					for(let k in m) {
						if(m[k] > max) {
							set = [k];
							max = m[k];
						}else if(m[k] == max){
							set.push(k);
						}
					}
					
					if(set.length > 0) {
						let ps = set[parseInt(Math.random()*set.length)];
						ps = ps.split(",");
						coor = [parseInt(ps[0]),parseInt(ps[1])];
					}
					
					return coor;
				},denfen:function(map, t, x, y) {
					
					let polet = 0;
					
					let ways = [
							[[1,0],[-1,0]],
							[[0,1],[0,-1]],
							[[1,1],[-1,-1]],
							[[1,-1],[-1,1]]
					];
					
					for(let i = 0; i < 4; i++) {
						
						let count = 0;
						
						let way = ways[i];
						
						let res1 = counter(map,t,x,y,way[0][0],way[0][1],0,4);
						let res2 = counter(map,t,x,y,way[1][0],way[1][1],0,4);
						count = res1[0] + res2[0];
						
						if(count >= 4) {
							polet+= this.gen[3][0];
						}else {
							
							let zp = 0;
							
							if(res1[3] == 0) {
								zp++;
							}
							
							if(res2[3] == 0) {
								zp++;
							}
							
							if(zp > 0 && count > 0) {
								polet+= this.gen[count-1][zp - 1];
							}
							
						}
						
					}
					
					return polet;
				}
			}
</script>
</html>